/**
 * 广寒宫
 * 网址:www.guanghangong.xyz
 */
package org.moon.framework.autoconfigure.log.domain;

import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.moon.framework.autoconfigure.log.properties.AccessLogProperties;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 日志对象
 * 
 * @author moon
 */
@Slf4j
public class AccessLogger {

	@Getter
	private AccessLogProperties properties;

	private static final String REQUEST_PREFIX = "Request Info [";
	private static final String REQUEST_SUFFIX = "] ";
	private static final String RESPONSE_PREFIX = "Response Info [";
	private static final String RESPONSE_SUFFIX = "] ";

	private StringBuilder normalMsg = new StringBuilder();

	public AccessLogger(AccessLogProperties properties) {
		this.properties = properties;
	}

	public void appendRequestCommonMessage(HttpServletRequest request) {
		normalMsg.append(REQUEST_PREFIX);
		StringBuilder msg = new StringBuilder();
		msg.append("uri=").append(request.getRequestURI());
		msg.append(";method=").append(request.getMethod());

		if (properties.isIncludeRequest()) {
			String queryString = request.getQueryString();
			if (queryString != null) {
				msg.append('?').append(queryString);
			}

			Map<String, String[]> params = request.getParameterMap();
			if (params != null && !params.isEmpty()) {
				msg.append(";params={");
				msg.append(getParams(params));
				msg.append("}");
			}

			String client = request.getRemoteAddr();
			if (StringUtils.hasLength(client)) {
				msg.append(";client=").append(client);
			}
			HttpSession session = request.getSession(false);
			if (session != null) {
				msg.append(";session=").append(session.getId());
			}
			String user = request.getRemoteUser();
			if (user != null) {
				msg.append(";user=").append(user);
			}
		}
		normalMsg.append(msg.toString());
	}

	public void appendRequestDetailMessage(boolean includeRequest, HttpServletRequest request) {
		StringBuilder msg = new StringBuilder();
		if (includeRequest && isNormalRequest(request)) {
			msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
			ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
			if (wrapper != null) {
				byte[] buf = wrapper.getContentAsByteArray();
				if (buf.length > 0) {
					int length = Math.min(buf.length,properties.getRequestBodyLength());
					String payload;
					try {
						payload = new String(buf, 0, length,wrapper.getCharacterEncoding());
					} catch (UnsupportedEncodingException ex) {
						payload = "[unknown]";
					}
					msg.append(";payload=").append(payload);
				}
			}
		}
		normalMsg.append(msg.toString());
		normalMsg.append(REQUEST_SUFFIX);
	}

	 public void appendResponseCommonMessage(ContentCachingResponseWrapper response, long cost) {
		normalMsg.append(RESPONSE_PREFIX);
		StringBuilder msg = new StringBuilder();
		msg.append("status=").append(response.getStatus());
		msg.append(";size=").append(response.getContentSize());
		msg.append(";cost=").append(cost);
		normalMsg.append(msg.toString());
	}

	@SuppressWarnings("resource")
	public void appendResponseDetailMessage(ContentCachingResponseWrapper response) {
		StringBuilder msg = new StringBuilder();
		String contentType = response.getContentType();
		msg.append(";headers=").append(new ServletServerHttpResponse(response).getHeaders());
		Optional.ofNullable(contentType)
				.filter(c -> c.startsWith("application/json"))
				.ifPresent(
						c -> {
							byte[] buf = response.getContentAsByteArray();
							if (buf.length > 0) {
								int length = Math.min(buf.length, properties.getResponseBodyLength());
								String payload;
								try {
									payload = new String(buf, 0, length, "utf-8");
								} catch (UnsupportedEncodingException ex) {
									payload = "[unknown]";
								}
								msg.append(";payload=").append(payload);
							}
						});
		normalMsg.append(msg.toString());
	}

	public void appendResponseLast() {
		normalMsg.append(RESPONSE_SUFFIX);
	}

	public void printLog() {
		log.info(this.normalMsg.toString());
	}

	private boolean isNormalRequest(HttpServletRequest request) {
		return !isMultipart(request) && !isBinaryContent(request);
	}

	private boolean isMultipart(final HttpServletRequest request) {
		return request.getContentType() != null && request.getContentType().startsWith("multipart/form-data");
	}

	private boolean isBinaryContent(final HttpServletRequest request) {
		if (request.getContentType() == null) {
			return false;
		}
		return request.getContentType().startsWith("image")
				|| request.getContentType().startsWith("video")
				|| request.getContentType().startsWith("audio");
	}

	private String getParams(final Map<String, String[]> params) {
		List<String> parts = Lists.newArrayList();
		params.forEach((k, v) -> {
			String param = k + "=[" + Arrays.stream(v).map(String::valueOf).collect(Collectors.joining(",")) + "]";
			parts.add(param);
		});
		return parts.stream().collect(Collectors.joining(","));
	}
}
